/*
 *    Copyright 2008,2009 Tim Jansen
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.actorsguildframework.internal.codegenerator;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

import org.actorsguildframework.Actor;
import org.actorsguildframework.ActorException;
import org.actorsguildframework.ActorRuntimeException;
import org.actorsguildframework.AsyncResult;
import org.actorsguildframework.ConfigurationException;
import org.actorsguildframework.annotations.ConcurrencyModel;
import org.actorsguildframework.annotations.Shared;
import org.actorsguildframework.immutable.ImmutableHelper;
import org.actorsguildframework.immutable.SerializableFreezer;
import org.actorsguildframework.internal.ActorClassDescriptor;
import org.actorsguildframework.internal.ActorProxy;
import org.actorsguildframework.internal.BeanClassDescriptor;
import org.actorsguildframework.internal.BeanFactory;
import org.actorsguildframework.internal.MessageCaller;
import org.actorsguildframework.internal.MessageImplDescriptor;
import org.actorsguildframework.internal.MultiThreadedActorState;
import org.actorsguildframework.internal.SingleThreadedActorState;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

/**
 * ProxyCreator is a singleton that creates proxy classes for all Actor classes of the agent.
 * It works system-wide, for all Controllers. 
 * 
 * For Actor classes themselves, there is one factory class (extends {@link BeanFactory}y)
 * and the actual proxy class extending {@link ActorProxy}. 
 * 
 * Additionally, there are {@link MessageCaller} classes for each message of the Actor.
 */
public final class ActorProxyCreator {
	/**
	 * The only instance of ProxyCreator.
	 */
	private static final ActorProxyCreator instance = new ActorProxyCreator();
	
	/**
	 * The version of the generated Java classes. 1.5 or 1.6, depending on the
	 * Java version. 
	 */
	private final static int codeVersion = System.getProperty("java.version").startsWith("1.5") ? Opcodes.V1_5 : Opcodes.V1_6;

	/**
	 * Private constructor.
	 */
	private ActorProxyCreator() {
	}
	
	/**
	 * Returns the ProxyCreator.
	 * @return the ProxyCreator
	 */
	public static ActorProxyCreator getInstance() {
		return instance;
	}
	
	/**
	 * Creates a new factory.
	 * @param actorClass
	 * @return the factory
	 * @throws ConfigurationException if the agent is not configured correctly
	 */
	@SuppressWarnings("unchecked")
	public BeanFactory createFactory(Class<?> actorClass) {
		ActorClassDescriptor acd = ActorClassDescriptor.create((Class<? extends Actor>)actorClass);
		try {
			
			generateProxyClass(actorClass, acd);
			return generateFactoryClass(actorClass, acd);
		}
		catch (NoSuchMethodException e) {
			throw new ActorException("Unexpected error while creating proxy", e);
		}
	}

	/**
	 * Returns a number for the method that's unique across all methods of the same
	 * class with the same name.
	 * @param method the method
	 * @return the method number
	 */
	private static int getMethodNumber(Method method) {
		ArrayList<Method> methods = new ArrayList<Method>();
		for (Method m: method.getDeclaringClass().getMethods()) 
			if (m.getName().equals(method.getName()))
				methods.add(m);
		Collections.sort(methods, new Comparator<Method>() {
			public int compare(Method o1, Method o2) {
				Class<?>[] args1 = o1.getParameterTypes();
				Class<?>[] args2 = o2.getParameterTypes();
				if (args1.length != args2.length)
					return args1.length - args2.length;
				for (int i = 0; i < args1.length; i++) 
					if (!args1[i].equals(args2[i]))
						return args1[i].getName().compareTo(args2[i].getName());
				return 0;
			}
		});
		return methods.indexOf(method);
	}

	/**
	 * Create or get a MessageCaller implementation for the given method.
	 * @param ownerClass the class that owns the message
	 * @param method the method to invoke
	 * @return the message caller
	 * @throws NoSuchMethodException 
	 * @throws SecurityException 
	 */
	@SuppressWarnings("unchecked")
	public static Class<MessageCaller<?>> createMessageCaller(Class<?> ownerClass, Method method) 
		throws SecurityException, NoSuchMethodException {
		
		String className = String.format("%s_%s_%d__MESSAGECALLER", 
				ownerClass.getName(),
				method.getName(), 
				getMethodNumber(method));
		String classNameInternal = className.replace('.', '/');
		java.lang.reflect.Type fullReturnType = method.getGenericReturnType();
		if ((!(fullReturnType instanceof ParameterizedType)) && AsyncResult.class.isAssignableFrom(((Class)((ParameterizedType)fullReturnType).getRawType())))
			throw new RuntimeException("Something's wrong here: should not be called for such a method");
		String returnSignature = GenericTypeHelper.getSignature(((ParameterizedType)fullReturnType).getActualTypeArguments()[0]);

		
		ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
		MethodVisitor mv;

		cw.visit(codeVersion, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER + Opcodes.ACC_SYNTHETIC, classNameInternal, 
				"L"+classNameInternal+"<"+returnSignature+">;", 
				"org/actorsguildframework/internal/MessageCaller", null);
		cw.visitSource(null, null);

		{
			mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
			mv.visitCode();
			Label l0 = new Label();
			mv.visitLabel(l0);
			mv.visitVarInsn(Opcodes.ALOAD, 0);
			mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "org/actorsguildframework/internal/MessageCaller", "<init>", "()V");
			mv.visitInsn(Opcodes.RETURN);
			Label l1 = new Label();
			mv.visitLabel(l1);
			mv.visitLocalVariable("this", "L"+classNameInternal+";", null, l0, l1, 0);
			mv.visitMaxs(1, 1);
			mv.visitEnd();
		}
		{
			mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "invoke", "(Lorg/actorsguildframework/Actor;[Ljava/lang/Object;)Lorg/actorsguildframework/AsyncResult;", 
					"(Lorg/actorsguildframework/Actor;[Ljava/lang/Object;)Lorg/actorsguildframework/AsyncResult<"+returnSignature+">;", null);
			mv.visitCode();
			Label l0 = new Label();
			mv.visitLabel(l0);

			mv.visitVarInsn(Opcodes.ALOAD, 1);
			mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(method.getDeclaringClass())+"__ACTORPROXY");
			
			int idx = 0;
			for (Class<?> t: method.getParameterTypes()) {
				mv.visitVarInsn(Opcodes.ALOAD, 2);
				mv.visitIntInsn(Opcodes.BIPUSH, idx);
				mv.visitInsn(Opcodes.AALOAD);
				if (t.isPrimitive()) {
					String wrapperDescr = GenerationUtils.getWrapperInternalName(t);
					mv.visitTypeInsn(Opcodes.CHECKCAST, wrapperDescr);
					mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, wrapperDescr, t.getName()+"Value", "()"+Type.getDescriptor(t));
				}
				else {
					if (isArgumentFreezingRequired(method, idx, t)) {
						mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(SerializableFreezer.class));
						mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(SerializableFreezer.class), "get", Type.getMethodDescriptor(SerializableFreezer.class.getMethod("get")));
					}
					if (!t.equals(Object.class))
						mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(t));
				}
				idx++;
			}
			mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(method.getDeclaringClass())+"__ACTORPROXY", 
					String.format(SUPER_CALLER_NAME_FORMAT, method.getName()), Type.getMethodDescriptor(method));

			mv.visitInsn(Opcodes.ARETURN);

			Label l2 = new Label();
			mv.visitLabel(l2);
			mv.visitLocalVariable("this", "L"+classNameInternal+";", null, l0, l2, 0);
			mv.visitLocalVariable("instance", "Lorg/actorsguildframework/Actor;", null, l0, l2, 1);
			mv.visitLocalVariable("arguments", "[Ljava/lang/Object;", null, l0, l2, 2);

			mv.visitMaxs(0, 0);
			mv.visitEnd();
		}
		{
			mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "getMessageName", "()Ljava/lang/String;", null, null);
			mv.visitCode();
			Label l0 = new Label();
			mv.visitLabel(l0);
			mv.visitLdcInsn(method.getName());
			mv.visitInsn(Opcodes.ARETURN);
			Label l1 = new Label();
			mv.visitLabel(l1);
			mv.visitLocalVariable("this", "L"+classNameInternal+";", null, l0, l1, 0);
			mv.visitMaxs(0, 0);
			mv.visitEnd();
		}
		cw.visitEnd();

		return (Class<MessageCaller<?>>)GenerationUtils.loadClass(className, cw.toByteArray());
	}

	/**
	 * Creates and loads the actor's proxy factory class.
	 * @param actorClass the Actor class
	 * @param acd the actor's class descriptor
	 * @return the new factory
	 */
	public static BeanFactory generateFactoryClass(Class<?> actorClass, ActorClassDescriptor acd) {
		String generatedBeanClassName = String.format("%s__ACTORPROXY", actorClass.getName());
		return BeanCreator.generateFactoryClass(actorClass, generatedBeanClassName, acd.getBeanClassDescriptor(), true);
	}

	private static final String MESSAGE_CALLER_NAME_FORMAT =  "messageCaller_%d__ACTORPROXY";
	private static final String SUPER_CALLER_NAME_FORMAT =  "%s__ACTORPROXYMETHOD_original";

	/**
	 * Creates a synchronized delegate method for a message method.
	 * @param actorClass the actor class
	 * @param classNameDescriptor the descriptor of the resulting class
	 * @param cw the class writer to write to
	 * @param method the method being invoked
	 * @param simpleDescriptor the descriptor of the method
	 * @param genericSignature the generic signature of the method
	 * @param isSynchronized true to make the method synchronized, false otherwise
	 */
	private static void writeSuperProxyMethod(Class<?> actorClass,
			String classNameDescriptor, ClassWriter cw, Method method,
			String simpleDescriptor, String genericSignature, boolean isSynchronized) throws NoSuchMethodException {
		MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC + (isSynchronized ? Opcodes.ACC_SYNCHRONIZED : 0), 
				String.format(SUPER_CALLER_NAME_FORMAT, method.getName()), simpleDescriptor, genericSignature, null);
		mv.visitCode();
		Label l0 = new Label();
		mv.visitLabel(l0);
		mv.visitVarInsn(Opcodes.ALOAD, 0);
		for (int j = 0; j < method.getParameterTypes().length; j++)
			mv.visitVarInsn(Type.getType(method.getParameterTypes()[j]).getOpcode(Opcodes.ILOAD), j+1);
		mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(actorClass), method.getName(), simpleDescriptor);
		mv.visitInsn(Type.getType(method.getReturnType()).getOpcode(Opcodes.IRETURN));
		Label l1 = new Label();
		mv.visitLabel(l1);
		mv.visitLocalVariable("this", classNameDescriptor, null, l0, l1, 0);
		for (int j = 0; j < method.getParameterTypes().length; j++)
			mv.visitLocalVariable("arg"+j, Type.getDescriptor(method.getParameterTypes()[j]), GenericTypeHelper.getSignatureIfGeneric(method.getGenericParameterTypes()[j]), l0, l1, j+1);
		mv.visitMaxs(0, 0);
		mv.visitEnd();
	}
	
	/**
	 * Creates and loads the actor's proxy class.
	 * @param actorClass the Actor class
	 * @param acd the actor's class descriptor
	 * @throws ConfigurationException if the agent is not configured correctly
	 */
	@SuppressWarnings("unchecked")
	private static Class<?> generateProxyClass(Class<?> actorClass, final ActorClassDescriptor acd) 
		throws NoSuchMethodException {
		BeanClassDescriptor bcd = acd.getBeanClassDescriptor();
		
		String className = String.format("%s__ACTORPROXY", 
				actorClass.getName());
		final String classNameInternal = className.replace('.', '/');
		String classNameDescriptor = "L" + classNameInternal + ";";

		final Type actorState = Type.getType(acd.getConcurrencyModel().isMultiThreadingCapable() ? MultiThreadedActorState.class : SingleThreadedActorState.class);
		
		ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
		MethodVisitor mv;
		cw.visit(codeVersion, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER  + Opcodes.ACC_SYNTHETIC, classNameInternal, null, Type.getInternalName(actorClass), new String[] { "org/actorsguildframework/internal/ActorProxy" });

		cw.visitSource(null, null);

		{
			for (int i = 0; i < acd.getMessageCount(); i++)
				cw.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, String.format(MESSAGE_CALLER_NAME_FORMAT, i), "Lorg/actorsguildframework/internal/MessageCaller;", "Lorg/actorsguildframework/internal/MessageCaller<*>;", null)
				  .visitEnd();
	
			cw.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL, "actorState__ACTORPROXY", actorState.getDescriptor(), null, null)
			  .visitEnd();
		}
		
		BeanCreator.writePropFields(bcd, cw);
		
		{
			mv = cw.visitMethod(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
			mv.visitCode();
			
			for (int i = 0; i < acd.getMessageCount(); i++) {
				Class<?> caller = createMessageCaller(acd.getMessage(i).getOwnerClass(), acd.getMessage(i).getMethod());
				String mcName = Type.getInternalName(caller);
				mv.visitTypeInsn(Opcodes.NEW, mcName);
				mv.visitInsn(Opcodes.DUP);
				mv.visitMethodInsn(Opcodes.INVOKESPECIAL, mcName, "<init>", "()V");
				mv.visitFieldInsn(Opcodes.PUTSTATIC, classNameInternal, String.format(MESSAGE_CALLER_NAME_FORMAT, i), "Lorg/actorsguildframework/internal/MessageCaller;");
			}
			mv.visitInsn(Opcodes.RETURN);
			mv.visitMaxs(0, 0);
			mv.visitEnd();
		}
		
		BeanCreator.writeConstructor(actorClass, bcd, classNameInternal, cw, new BeanCreator.SnippetWriter() {
			@Override
			public void write(MethodVisitor mv) {
				mv.visitVarInsn(Opcodes.ALOAD, 0);
				mv.visitTypeInsn(Opcodes.NEW, actorState.getInternalName());
				mv.visitInsn(Opcodes.DUP);
				mv.visitVarInsn(Opcodes.ALOAD, 1);
				mv.visitVarInsn(Opcodes.ALOAD, 0);
				mv.visitMethodInsn(Opcodes.INVOKESPECIAL, actorState.getInternalName(), "<init>", "(Lorg/actorsguildframework/internal/Controller;Lorg/actorsguildframework/Actor;)V");
				mv.visitFieldInsn(Opcodes.PUTFIELD, classNameInternal, "actorState__ACTORPROXY", actorState.getDescriptor());
			}
		});
		
		BeanCreator.writePropAccessors(bcd, classNameInternal, cw);
			
		{
			mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "getState__ACTORPROXYMETHOD", "()Lorg/actorsguildframework/internal/ActorState;", null, null);
			mv.visitCode();
			Label l0 = new Label();
			mv.visitLabel(l0);
			mv.visitVarInsn(Opcodes.ALOAD, 0);
			mv.visitFieldInsn(Opcodes.GETFIELD, classNameInternal, "actorState__ACTORPROXY", actorState.getDescriptor());
			mv.visitInsn(Opcodes.ARETURN);
			Label l1 = new Label();
			mv.visitLabel(l1);
			mv.visitLocalVariable("this", classNameDescriptor, null, l0, l1, 0);
			mv.visitMaxs(0, 0);
			mv.visitEnd();
		}
		
		for (int i = 0; i < acd.getMessageCount(); i++) {
			MessageImplDescriptor mid = acd.getMessage(i);
			Method method = mid.getMethod();
			String simpleDescriptor = Type.getMethodDescriptor(method);
			String genericSignature = GenericTypeHelper.getSignature(method);

			writeProxyMethod(classNameInternal, classNameDescriptor, cw, i, actorState,
					acd.getConcurrencyModel(), mid, method, simpleDescriptor, genericSignature);
			
			writeSuperProxyMethod(actorClass, classNameDescriptor, cw, method,
					simpleDescriptor, genericSignature, 
					!acd.getConcurrencyModel().isMultiThreadingCapable());
		}

		{
			mv = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_SYNCHRONIZED, "toString", "()Ljava/lang/String;", null, null);
			mv.visitCode();
			Label l0 = new Label();
			mv.visitLabel(l0);
			mv.visitVarInsn(Opcodes.ALOAD, 0);
			mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "toString", "()Ljava/lang/String;");
			mv.visitInsn(Opcodes.ARETURN);
			Label l1 = new Label();
			mv.visitLabel(l1);
			mv.visitLocalVariable("this", classNameDescriptor, null, l0, l1, 0);
			mv.visitMaxs(0, 0);
			mv.visitEnd();
		}
		cw.visitEnd();
		
		try {
			return (Class<? extends ActorProxy>) GenerationUtils.loadClass(className, cw.toByteArray());
		} catch (Exception e) {
			throw new ConfigurationException("Failure loading ActorProxy", e);
		}
	}

	/**
	 * Checks whether the given class, when given as argument of the method, must be frozen.
	 * @param m the method to check
	 * @param idx the parameter index
	 * @param c the class to check
	 * @return true if it must be frozen, false otherwise
	 */
	private static boolean isArgumentFreezingRequired(Method m, int idx, Class<?> c) {
		if (Actor.class.isAssignableFrom(c))
			return false;
		if (ImmutableHelper.isImmutableType(c))
			return false;
		Annotation[][] paramAnnotations = m.getParameterAnnotations();
		for (Annotation a: paramAnnotations[idx])
			if (a.annotationType().equals(Shared.class))
				return false;
		return (!c.isInterface()) || Serializable.class.isAssignableFrom(c);
	}
	
	/**
	 * Writes a proxy method for messages.
	 * @param classNameInternal the internal class name
	 * @param classNameDescriptor the class name descriptor
	 * @param cw the ClassWriter
	 * @param index the message index
	 * @param type the ActorState type to use
	 * @param concurrencyModel the concurrency model of the message
	 * @param messageDescriptor the message's descriptor
	 * @param method the method to override
	 * @param simpleDescriptor a simple descriptor of the message
	 * @param genericSignature the signature of the message
	 */
	private static void writeProxyMethod(String classNameInternal,
			String classNameDescriptor, ClassWriter cw, int index,
			Type actorState, ConcurrencyModel concurrencyModel, 
			MessageImplDescriptor messageDescriptor, Method method, String simpleDescriptor,
			String genericSignature) throws NoSuchMethodException {
		MethodVisitor mv;
		{
			mv = cw.visitMethod(Opcodes.ACC_PUBLIC, method.getName(), simpleDescriptor, genericSignature, null);
			mv.visitCode();
			Label l0 = new Label();
			mv.visitLabel(l0);
			mv.visitIntInsn(Opcodes.BIPUSH, method.getParameterTypes().length);
			mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
			for (int j = 0; j < method.getParameterTypes().length; j++) {
				mv.visitInsn(Opcodes.DUP);
				mv.visitIntInsn(Opcodes.BIPUSH, j);
				Class<?> paraType = method.getParameterTypes()[j];
				if (paraType.isPrimitive()) {
					String wrapperClass = GenerationUtils.getWrapperInternalName(paraType);
					Type primType = Type.getType(paraType);
					mv.visitVarInsn(primType.getOpcode(Opcodes.ILOAD), j+1);
					mv.visitMethodInsn(Opcodes.INVOKESTATIC, wrapperClass, "valueOf", "("+primType.getDescriptor()+")"+"L"+wrapperClass+";");			
				}
				else if (isArgumentFreezingRequired(method, j, paraType)) {
					mv.visitVarInsn(Opcodes.ALOAD, j+1);
					mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(SerializableFreezer.class), "freeze", Type.getMethodDescriptor(SerializableFreezer.class.getMethod("freeze", Object.class)));
				}
				else if (paraType.isInterface()) {
					mv.visitVarInsn(Opcodes.ALOAD, j+1);
					mv.visitInsn(Opcodes.DUP);
					mv.visitTypeInsn(Opcodes.INSTANCEOF, "org/actorsguildframework/Actor");
					Label lEndif = new Label();
					mv.visitJumpInsn(Opcodes.IFNE, lEndif);
					mv.visitTypeInsn(Opcodes.NEW, Type.getInternalName(ActorRuntimeException.class));
					mv.visitInsn(Opcodes.DUP);
					mv.visitLdcInsn(String.format("Argument %d is an non-Serializable interface, but you did not give an Actor. If a message's argument type is an interface that does not extend Serializable, only Actors are acceptable as argument.", j));
					mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(ActorRuntimeException.class), "<init>", "(Ljava/lang/String;)V");
					mv.visitInsn(Opcodes.ATHROW);
					mv.visitLabel(lEndif);
				}
				else
					mv.visitVarInsn(Opcodes.ALOAD, j+1);

				mv.visitInsn(Opcodes.AASTORE);
			}
			Label l1 = new Label();
			mv.visitLabel(l1);
			mv.visitVarInsn(Opcodes.ASTORE, method.getParameterTypes().length+1); // method.getParameterTypes().length+1 ==> 'args' local variable
			mv.visitVarInsn(Opcodes.ALOAD, 0);
			mv.visitFieldInsn(Opcodes.GETFIELD, classNameInternal, "actorState__ACTORPROXY", actorState.getDescriptor());
			mv.visitFieldInsn(Opcodes.GETSTATIC, classNameInternal, String.format(MESSAGE_CALLER_NAME_FORMAT, index), "Lorg/actorsguildframework/internal/MessageCaller;");
			mv.visitFieldInsn(Opcodes.GETSTATIC, "org/actorsguildframework/annotations/ThreadUsage", messageDescriptor.getThreadUsage().name(), "Lorg/actorsguildframework/annotations/ThreadUsage;");
			mv.visitVarInsn(Opcodes.ALOAD, method.getParameterTypes().length+1);
			mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, actorState.getInternalName(), "queueMessage", "(Lorg/actorsguildframework/internal/MessageCaller;Lorg/actorsguildframework/annotations/ThreadUsage;[Ljava/lang/Object;)Lorg/actorsguildframework/internal/AsyncResultImpl;");
			mv.visitInsn(Opcodes.ARETURN);
			Label l4 = new Label();
			mv.visitLabel(l4);
			mv.visitLocalVariable("this", classNameDescriptor, null, l0, l4, 0);
			for (int j = 0; j < method.getParameterTypes().length; j++)
				mv.visitLocalVariable("arg"+j, Type.getDescriptor(method.getParameterTypes()[j]), GenericTypeHelper.getSignatureIfGeneric(method.getGenericParameterTypes()[j]), l0, l4, j+1);
			mv.visitLocalVariable("args", "[Ljava/lang/Object;", null, l1, l4, method.getParameterTypes().length+1);
			mv.visitMaxs(0, 0);
			mv.visitEnd();
		}
	}

}
